package com.kg.fiber.service.pcie8622;

import java.text.SimpleDateFormat;
import java.util.Arrays;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.eventbus.Subscribe;
import com.ivan.toolkit.common.Events;
import com.kg.fiber.common.TimedDoubleBuffer;
import com.kg.fiber.service.Boss;
import com.kg.fiber.service.clock.Clocker;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;

import lombok.extern.log4j.Log4j;

@Component
@Log4j
public class Pcie8622 implements Runnable {
	
	
	
	@Autowired
	private Clocker clock;
	
	@Autowired
	private Boss boss;

	Pointer hDevice = Pointer.NULL;
	
	final int HALF_SIZE_WORDS = 32 * 1024; // 定义缓冲区每页段长度�?32K�?
	
	short ADBuffer[] = new short[HALF_SIZE_WORDS];
	
	private boolean harfirq = true;
	
	private long receiveInterval = 1;
	
	Pcie8622Sdk.PCIE8622_PARA_AD.ByReference ADPara = new Pcie8622Sdk.PCIE8622_PARA_AD.ByReference();
	
	Thread thread = null;
	
	/**
	 * 配置文件更新标记，当�?测到配置文件发生变化，会自动重启PCI1714
	 */
	private boolean newconfig = false;
	
	private long receivedBytes = 0;
	
	private long receiveCounts = 0;
	
	private long lastTimestamp = 0;
	
	@PostConstruct
	private void postConstruct() {
		
		Events.register(this);

	}
	
	@PreDestroy
	private void preDestroy() {
				
		// 停止采集
		stop();
		
		Events.unregister(this);
	}
	
	public void applyConfig() {
		
		if(newconfig) {
			
			log.info("Config updated, stop and restart module.");
			
			this.stop();
			
			newconfig = false;
			
		}
		
	}
	
	@Subscribe
	private void dispatch(Events event) {
		if(event.getKey() instanceof Pcie8622Config) {
			newconfig = true;
		}
	}
	
	public boolean isStarted() {
		return hDevice != Pointer.NULL;
	}
	
	public void start() {
		
		// only
		if(!Platform.isLinux()) {
			log.info("PCIE8622 support linux platform only.");
			return;
		}
		
		Pcie8622Config config = Pcie8622Config.instance.get();
		
		this.harfirq = config.isHarfReceiveMode();
		
		this.receiveInterval = config.getReceiveInterval();
		
		Pointer pointer = Pointer.NULL;
		
		try {
			
			// Open device
			pointer = Pcie8622Sdk.Clibrary.INSTANCE.pcie_device_open(config.getLogicId());
			if(pointer == Pointer.NULL)
			{
				log.warn("Open device failed, device logic id  is " + config.getLogicId());
				return;
			}
			
		} catch(Throwable e) {
			
			log.warn("Open device failed, device logic id  is " + config.getLogicId(), e);
			return;
			
		}
		
		// initialize parameter
		ADPara.ADMode = config.getAdMode();
		ADPara.FirstChannel = config.getChannelFirst();
		ADPara.LastChannel = Math.max(config.getChannelFirst(), config.getChannelLast());
		ADPara.Frequency = config.getFrequency();
		ADPara.InputRange = config.getInputRange();
		ADPara.GroupInterval = config.getGroupInterval();
		ADPara.LoopsOfGroup = config.getGroupLoops();
		ADPara.Gains = config.getGains();
		ADPara.TriggerMode = config.getTrigerMode();
		ADPara.TriggerSource = config.getTrigerSource();
		ADPara.TriggerType = config.getTrigerType();
		ADPara.TriggerDir = config.getTrigerDir();
		ADPara.TrigLevelVolt = config.getTrigLevelVolt();
		ADPara.bClockOutput = config.getClockOutput();
		ADPara.ClockSource = config.getClockSource();
		ADPara.GroundingMode = config.getGroundingMode();
		if(Pcie8622Sdk.Clibrary.INSTANCE.pcie_ad_init(pointer, ADPara) == 0)
		{
			log.error("Initialize hardware parameter failed, device logic id  is " + config.getLogicId());
			Pcie8622Sdk.Clibrary.INSTANCE.pcie_device_close(pointer);
			return;
		}
		
		// Enable AD
		if(Pcie8622Sdk.Clibrary.INSTANCE.pcie_enable_ad(pointer) == 0)
		{
			log.error("Enable AD mode failed, device logic id  is " + config.getLogicId());
			Pcie8622Sdk.Clibrary.INSTANCE.pcie_device_close(pointer);
			return;
		}
		
		if(harfirq) {
			
			// Enable harfirq
			if(Pcie8622Sdk.Clibrary.INSTANCE.pcie_enable_halfirq(pointer) == 0)
			{
				log.error("Enable harfirq failed, device logic id  is " + config.getLogicId());
				Pcie8622Sdk.Clibrary.INSTANCE.pcie_device_close(pointer);
				return;
			}
			
		}
		
		// clear FIFO
		Pcie8622Sdk.Clibrary.INSTANCE.pcie_ad_clear_fifo(pointer);
		
		
		// 
		this.hDevice = pointer;
		
		receivedBytes = 0;
		receiveCounts = 0;
		
		// start receive thread
		thread = new Thread(this);
		thread.setName("pcie8622");
		thread.start();
		
	}
	
	public void stop() {
		
		if(hDevice == Pointer.NULL) 
			return;
		
		Pointer pointer = hDevice;
		
		hDevice = Pointer.NULL;
		
		try {
			thread.join(2000);
		} catch(Exception e) {
			
		} finally {
			thread = null;
		}
		
		if(Pcie8622Sdk.Clibrary.INSTANCE.pcie_stop_ad(pointer)<0)
		{
			log.warn("Stop AD failed.");
		}
		
		if(harfirq)
		{
			if(Pcie8622Sdk.Clibrary.INSTANCE.pcie_disable_halfirq(pointer)<0)
			{
				log.warn("Disalbe harfirq failed.");
			}
		}
		
		if(Pcie8622Sdk.Clibrary.INSTANCE.pcie_device_close(pointer)==0)
		{
			log.warn("Close device failed.");
		}		
		
	}
	
	/**
	 * receive data
	 * @return full receive if return true 
	 */
	public boolean receive() {
		
		receiveCounts++;
		
		Arrays.fill(ADBuffer, (short)0);
		
		int len = HALF_SIZE_WORDS - HALF_SIZE_WORDS % (ADPara.LastChannel - ADPara.FirstChannel + 1);
		
		if(harfirq) {
			
			 //half
			if((Pcie8622Sdk.Clibrary.INSTANCE.pcie_read_reg(hDevice, 0x01c)>>2 & 0x01) == 0)
			{
				Pcie8622Sdk.Clibrary.INSTANCE.pcie_ad_clear_fifo(hDevice);
			}
			
			if((Pcie8622Sdk.Clibrary.INSTANCE.pcie_read_reg(hDevice, 0x01c)>>1 & 0x01) == 0)
			{
				
				/*
				 * 根据�?术人员描述，这个函数返回的是获取数据的长�?
				 */
				int ret = Pcie8622Sdk.Clibrary.INSTANCE.pcie_read_ad_half(hDevice, ADBuffer,len);
				if(ret < 0)
				{
					log.error("Read AD data failed. HARFIRQ=TRUE");
					Pcie8622Sdk.Clibrary.INSTANCE.pcie_device_close(hDevice);
					hDevice = Pointer.NULL;
					return false;
				}
				
				if(ret > 0)
					process(ret);
				
				receivedBytes += ret;
				
				return ret >= len;
				
			}			

			
		} else {
			
			
			// fei kong
			int retlen[] = new int[1];
			
			if(Pcie8622Sdk.Clibrary.INSTANCE.pcie_read_ad_noirq(hDevice,ADBuffer, len, retlen) == 0)
			{
				log.error("Read AD data failed. HARFIRQ=FALSE");
				Pcie8622Sdk.Clibrary.INSTANCE.pcie_device_close(hDevice);
				hDevice = Pointer.NULL;
				return false;
			}
			
			process(retlen[0]);
			
			receivedBytes += retlen[0];
			
			return retlen[0] >= len;
			
		}
		
		return false;
		
	}
	
	public void process(int len) {
		
		if(len == 0) {
			
			log.warn("the data length is zero, ignore");
			return;
			
		}
		
		int chCount = ADPara.LastChannel - ADPara.FirstChannel + 1;
		
		long timestamp = clock.nowInMillis();
		
		lastTimestamp = timestamp;
		
		TimedDoubleBuffer[] bs = new TimedDoubleBuffer[chCount];
		for(int i = 0; i < chCount; i++) {
			bs[i] = TimedDoubleBuffer.pool.get();
			bs[i].setTimestamp(timestamp);
		}
		
		for(int i = 0; i < len; i++) {
			int channel = i % chCount;
			bs[channel].add(convert(ADBuffer[i]));
		}
		
		for(int i = 0; i < chCount; i++) {
			boss.feed(i, bs[i]);
		}
		
		
	}
	
	public double convert(short value) {
		
		int data = value & 0xFFFF;
		
		switch(ADPara.InputRange)
		{
		case 0:
			return (double)((20000.00/65536)*data)-10000.00;
		case 1:
			return (double)((10000.00/65536)*data)-5000.00;
		case 2:
			return (double)((5000.00/65536)*data)-2500.00;
		case 3:
			return (double)((10000.00/65536)*data);
		case 4:
			return (double)((5000.00/65536)*data);
		default:
			return (double)value;
		}
		
	}

	@Override
	public void run() {
		
		log.info("Module thread started");
		
		while(hDevice != Pointer.NULL) {
			
			boolean fullReceive = false;
			
			try {
				
				fullReceive = this.receive();
				
			} catch(Exception e) {
				
				log.warn("Receive data failed.", e);
				
			}
			
			if(!fullReceive && receiveInterval > 0) {
				
				try {
					Thread.sleep(receiveInterval);
				} catch(InterruptedException e) {
					break;
				}
			
			}
			
		}
		
		log.info("Module thread stopped");
		
		
	}
	
	public void runtime(StringBuilder sb) {
		sb.append("[8622]\r\n");
		sb.append("state=").append(isStarted()).append("\r\n");
		if(isStarted()) {
			sb.append("receivedBytes=").append(receivedBytes).append("\r\n");
			sb.append("receiveCounts=").append(receiveCounts).append("\r\n");
			sb.append("lastTimestamp=").append(sdf.format(lastTimestamp)).append("\r\n");
			//sb.append("channelCount=").append(channelCount).append("\r\n");
			//sb.append("clockRatePerChan=").append(clockRatePerChan).append("\r\n");
		}
		//sb.append("countOfCacheOverFlow=").append(countOfCacheOverFlow.get()).append("\r\n");
		//sb.append("countofOverRun=").append(countOfOverRun.get()).append("\r\n");
	}
	
	public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
	
}
